package com.xm_jd.utils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class BaseDao<T> {

	/**
	 * 用来接收传递进来的指定实体类类型
	 */
	private Class<T> c;
	/**
	 * 有参构造
	 * @param c  要反射的实体类，比如Goods.class
	 */
	public BaseDao(Class<T> c) {
		this.c = c;
	}
	
	Connection con;
	PreparedStatement ps;
	ResultSet rs;
	
	/**
	 * 获得连接，连接池的方式
	 * @return
	 */
	private Connection getConnection(){
		try {
			//加载驱动，Class.forName(驱动的类路径)
			Context ctx = new InitialContext();
			DataSource d = (DataSource) 
					ctx.lookup("java:comp/env/jdbc/mysql");
			//创建连接对象（类似DriverManager.getConnection()）
			return d.getConnection();
		} catch (NamingException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 查询
	 * @return
	 */
	public List<T> selectAll(String sql,Object[] obj){
		try {
			//获得连接
			con = getConnection();
			//用连接去创建执行对象（Sql语句变化的）
			ps = con.prepareStatement(sql);
			//绑定参数
			bind(obj);
			//执行Sql语句
			rs = ps.executeQuery();
			//集合（变化的）
			List<T> list = new ArrayList<>();
			while(rs.next()){
				list.add(toBean());
			}
			return list;
		}  catch (SQLException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} finally{
			//关闭连接
			closeAll();
		}
		return null;
	}
	
	/**
	 * 绑定参数
	 * @param obj
	 * @throws SQLException 
	 */
	private void bind(Object[] obj) throws SQLException {
		//要判断非空，很重要，因为有的增删改查不需要参数
		if(obj!=null){
			for (int i = 0; i < obj.length; i++) {
				//绑定参数
				ps.setObject((i+1), obj[i]);
			}
		}
	}
	/**
	 * 封装实体类
	 * @return
	 */
	private T toBean() throws InstantiationException, IllegalAccessException, IllegalArgumentException, SQLException{
		//获得类的反射对象，
		//T是对象模型，指所有类型的泛型，可以代表所有泛型中的类型
		T t = c.newInstance();
		//获得传入的实体类类型中所有的字段，包括私有的
		Field[] f = c.getDeclaredFields();
		//获得结果集中所有列信息
		ResultSetMetaData rsmd = rs.getMetaData();
		for (int i = 0; i < rsmd.getColumnCount(); i++) {
			for (Field field : f) {
				//将结果集中的列名和实体类中的字段名匹配，相符的才封装
				if(rsmd.getColumnName(i+1).equals(field.getName())){
					//打开私有字段的开关
					field.setAccessible(true);
					//将结果集中的值赋给实体类中对应的字段
					//注意！反射的类型必须和数据库指定的类型一致，尤其注意时间类型
					field.set(t, rs.getObject(field.getName()));
				}
			}
		}
		return t;
	}
	
	/**
	 * 增删改
	 * @return
	 */
	public int executeUpdate(String sql,Object[] obj){
		try {
			con = getConnection();
			//用连接去创建执行对象（Sql语句变化的）
			ps = con.prepareStatement(sql);
			//绑定参数
			bind(obj);
			//执行Sql语句
			return ps.executeUpdate();
		}  catch (SQLException e) {
			e.printStackTrace();
		} finally{
			//关闭连接
			closeAll();
		}
		return 0;
	}
	
	
	private void closeAll(){
		if(rs!=null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if(ps!=null){
			try {
				ps.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if(con!=null){
			try {
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 多Sql修改操作(慎用)
	 * 所有命令要么同时提交成功
	 * 要么同时回滚
	 */
	public int [] updateAll(Map<String, Object[]> map) {
		//用来记录每一次增删改之后是否成功，0为失败，1为成功
		int[] row = new int[map.size()];
		int index = 0;//用来操作上面记录的下标，因为Map集合没有下标
		int error = 0;//记录是否有错误
		try {
			con = getConnection();
			if (con != null) {
				//获得Map集合中的键值对集合
				Set<Entry<String, Object[]>> set = map.entrySet();
				//关闭连接对象的自动提交的功能 
				con.setAutoCommit(false);
				for (Entry<String, Object[]> entry : set) {
					//获得键，生成执行对象
					ps=con.prepareStatement(entry.getKey());
					//获得键值对中的值，即Object数组，绑定参数
					this.bind(entry.getValue());
					//执行增删改，并把结果存起来
					row[index]=ps.executeUpdate();
					//如果执行结果为0，表示修改失败，立即抛出异常
					if(row[index]==0){
						throw new Exception("修改失败，数据回滚！");
					}
					index++;
				}
			} else {
				System.out.println("数据库连接失败");
			}
		} catch (Exception e) {
			error++;//进入catch表示修改失败，则记录错误
			e.printStackTrace();
		} finally {
			//根据错误的次数，只要有一次，立即回滚，取消之前所有的操作
			if(error>0){
				try {
					//将前面已经执行的命令回滚
					con.rollback();
				} catch (SQLException e) {
					e.printStackTrace();
				} 
			}else{
				try {
					//全部提交 
					con.commit();
				} catch (SQLException e) {
					e.printStackTrace();
				} 
			}
			closeAll();
		}
		return row;
	}
	

	/**
	 * 可以查询任意单独聚合函数
	 * select count(*) as count from xxxx;
	 * select sum(xxx) as sum from xxxx;
	 * @param sql
	 * @return
	 */
	public int getCounts(String sql){
		con=getConnection();
		if(con!=null){
			try {
				ps=con.prepareStatement(sql);
				rs=ps.executeQuery();
				//将结果集的游标往下移一格，就可以得到第一条记录
				rs.next();
				//一般情况下，查询聚合函数，结果只有一个列，比如count(*)
				//所以直接拿第一列就可以了
				return rs.getInt(1);
			} catch (Exception e) {
				e.printStackTrace();
			} finally{
				closeAll();
			}
		}
		return 0;
	}

}
